﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MonoTorrent;
using MonoTorrent.Client;
using System.IO;
using MonoTorrent.BEncoding;
using MonoTorrent.Common;
using System.Net;
using MonoTorrent.Client.Connections;
using MonoTorrent.Dht;
using MonoTorrent.Dht.Listeners;
using System.Windows;
using System.Windows.Forms;

namespace Download_Libary
{
    public sealed class TorrentDownload : IDisposable
    {
        #region Properties

        public int TotalUploadSpeed 
        {
            get { return m_Client.TotalUploadSpeed; }
        }

        public int TotalDownloadSpeed 
        {
            get { return m_Client.TotalDownloadSpeed; }
        }

        public double Progress
        {
            get { return m_Manager.Progress; }
        }
        #endregion
        #region Fields

        private ClientEngine m_Client;

        private int m_ListenPort;

        private string m_DhtSavePath;

        private string m_FastResumePath;

        private DirectoryInfo m_savePath;

        private TorrentManager m_Manager;

        private DhtListener m_DhtListener;

        private DhtEngine m_DhtEngine;

        #endregion

        public TorrentDownload(string defaultSavePath, int listenPort)
        {
            m_ListenPort = listenPort;

            EngineSettings engineSettings = new EngineSettings(defaultSavePath, listenPort);

            m_savePath = new DirectoryInfo(defaultSavePath);

            m_Client = new ClientEngine(engineSettings);
        }

        public void DownloadTorrent(byte[] byteArray)
        {
            Torrent torrent = Torrent.Load(byteArray);

            m_DhtSavePath = Path.Combine(m_savePath.FullName, torrent.Name + ".dht");
            m_FastResumePath = Path.Combine(m_savePath.FullName, torrent.Name + ".resume");

            m_Manager = new TorrentManager(torrent, m_savePath.FullName, new TorrentSettings());

            m_Client.Register(m_Manager);
            startDht();
            LoadFastResume();
            torrentState();
            m_Manager.Start();
        }

        public void ChangeDownloadSpeed(int newDownloadSpeed)
        {
            m_Client.Settings.GlobalMaxDownloadSpeed = newDownloadSpeed;
        }

        public void ChangeUploadSpeed(int newUploadSpeed)
        {
            m_Client.Settings.GlobalMaxUploadSpeed = newUploadSpeed;
        }

        public void SaveFastResume()
        {
            // Store the fast resume for each torrent in a list,
            // then serialise the list to the disk.
            BEncodedList list = new BEncodedList();

            // Get the fast resume data for the torrent
            FastResume data = m_Manager.SaveFastResume();

            // Encode the FastResume data to a BEncodedDictionary.
            BEncodedDictionary fastResume = data.Encode();

            // Add the FastResume dictionary to the main dictionary using
            // the torrents infohash as the key
            list.Add(fastResume);

            // Write all the fast resume data to disk
            File.WriteAllBytes(m_FastResumePath, list.Encode());
        }

        public void LoadFastResume()
        {
            // Read the main dictionary from disk and iterate through
            // all the fast resume items
            if (File.Exists(m_FastResumePath))
            {
                BEncodedList list = (BEncodedList)BEncodedValue.Decode(File.ReadAllBytes(m_FastResumePath));
                foreach (BEncodedDictionary fastResume in list)
                {

                    // Decode the FastResume data from the BEncodedDictionary
                    FastResume data = new FastResume(fastResume);

                    // Find the torrentmanager that the fastresume belongs to
                    // and then load it
                    if (m_Manager.InfoHash == data.Infohash)
                        m_Manager.LoadFastResume(data);
                }
            }
        }

        private void torrentState()
        {
            m_Manager.TorrentStateChanged += delegate(object o, TorrentStateChangedEventArgs e)
            {
                if (e.NewState == TorrentState.Error)
                {
                    MessageBox.Show("Error appeared");
                }
                else if (e.NewState == TorrentState.Downloading)
                {
                    MessageBox.Show("started Downloading");
                }
                else if (e.NewState == TorrentState.Seeding)
                {
                    MessageBox.Show("Download complete. Started with seeding");
                }
            };
        }

        public void StopTorrent()
        {
            m_Manager.TorrentStateChanged += delegate(object o, TorrentStateChangedEventArgs e)
            {
                if (e.NewState == TorrentState.Stopped)
                {
                    m_Client.Unregister(m_Manager);
                    m_Manager.Dispose();
                }
                if (e.NewState == TorrentState.Stopping)
                {
                    SaveFastResume();
                    StopDht();
                }
            };

            m_Manager.Stop();
        }

        private void startDht()
        {
            IPEndPoint listenAddress = new IPEndPoint(IPAddress.Any, m_ListenPort);

            m_DhtListener = new DhtListener(listenAddress);

            m_DhtEngine = new DhtEngine(m_DhtListener);

            m_Client.RegisterDht(m_DhtEngine);
            m_DhtListener.Start();

            byte[] nodes = null;

            if (File.Exists(m_DhtSavePath))
                nodes = File.ReadAllBytes(m_DhtSavePath);

            m_DhtEngine.Start();
        }

        private void StopDht()
        {
            //stop the torrent then dispose the objects
            this.StopTorrent();

            m_DhtListener.Stop();
            m_DhtEngine.Stop();

            File.WriteAllBytes(m_DhtSavePath, m_DhtEngine.SaveNodes());
        }

        public void Dispose()
        {
            m_Manager.Dispose();
            m_DhtEngine.Dispose();
            m_Client.Dispose();
        }
    }
}
